﻿from __future__ import print_function
from __future__ import unicode_literals

import sys, io, types, re
from opparser import isop


__all__ = ["LadderMacro", "getmacro", "splitLadderAndComment"]

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

macros = {}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 간이 매크로를 처리한다.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

class LineParser(object):
    ws_split_re = re.compile(r"[ \t]+")
    word_token_re = re.compile(r"\s*(\w+|\W)")
    comment_re = re.compile(r"\s*//.*")
    
    def init(self, data):
        self.lines = data.splitlines()
        self.lineno = 0
        self.line = ""
        
    def next(self):
        try:
            self.line = self.lines[self.lineno]
            self.lineno += 1
            self.pos = 0
            if self.comment_re.match(self.line):
                print("comment", self.line)
                return ""
            return self.line
        except IndexError:
            raise StopIteration()
        
    def back(self):
        if self.lineno > 0:
            self.lineno -= 1
            
    def token(self):
        m = self.word_token_re.match(self.line, self.pos)
        if m:
            self.pos = m.end(1)
            return m.group(1)
        return None
        
    def rest(self):
        return self.line[self.pos:]

    def __iter__(self):
        return self
       

def eval_index(index):
    if not index:
        return None, 0
    
    if index[0] in '0123456789': 
        return None, int(index)
    elif index[0] == "$": # $는 16진수 표시
        return None, int(index[1:], 16)
    else:
        return index[0], eval_index(index[1:])[1]
       
       
class DEVICE(object):
    def __init__(self, device):
        self.device = device
        
    def __getitem__(self, key):
        dev, num = eval_index(self.device)
        if type(key) != int:
            kdev, key = eval_index(str(key))
            if kdev:
               dev = kdev
        num += key
        
        if self.device[1:2] == "$":
            return "{0}${1:04X}".format(dev, num)
        else:
            return "{0}{1:04d}".format(dev, num)
            
    def __str__(self):
        return self.device
        
    def __unicode__(self):
        return self.device
       
       
class LadderMacro(LineParser):
    #expression_begin_re = re.compile("\\$\\{")
    expression_begin_re = re.compile("\\{")
    expression_end_re = re.compile("\\}")

    def __init__(self, macro, args=[], vars={}):
        self.macro = macro
        self.args = args
        self.vars = vars

    def hasvar(self, name):
        return (name in self.vars) or hasattr(self, name)
        
    def getvar(self, name):
        if name in self.vars:
            return unicode(self.vars[name])
        elif hasattr(self, name):
            return getattr(self, name)
        return name

    def _getvalue(self, name):
        index_start = name.find("[")
        if index_start != -1:
            index_end = name.find("]")
            index = name[index_start + 1:index_end]
            name = name[:index_start]
        else:
            index = None
        name = self.getvar(name)
            
        if index:
            iname, index = eval_index(index)
            # index의 device로 name의 device를 바꾼다.
            if name and iname:
                name = iname + name[1:]
                
            try:
                dev, num = eval_index(name)
                if index:
                    num += index
                if name[1:2] == "$":
                    return "{0}{1:04X}".format(dev, num)
                else:
                    return "{0}{1:04d}".format(dev, num)
            except ValueError:
                return name
        return name
        
    def getvalue(self, name):
        try:
            return self._getvalue(name)
        except:
            print(name)
            raise
        
    def renderLine(self, line):
        result = []
        length = len(line)
        pos = 0
        while pos < length:
            start = self.expression_begin_re.search(line, pos)
            if start:
                text = line[pos:start.start()]
                result.append(text)
                pos = start.end()
                end = self.expression_end_re.search(line, pos)
                if end:
                    name = line[start.end():end.start()]
                    result.append(self.getvalue(name))
                    pos = end.end()
                else:
                    text = line[start.start():start.end()]
                    result.append(text)
            else:
                result.append(line[pos:])
                pos = length
        return "".join(result)
        
    def setargs(self, args):
        for name, value in zip(self.args, args):
            setattr(self, name, value)
            
        if hasattr(self, "comment") and self.comment.find(";") != -1:
            comment, _type = self.comment.split(";")
            _type = _type.strip().split("/")
            comment = comment.strip()
            self.comment = comment
            self.comment1 = comment + "." + _type[0]
            self.comment2 = comment + "." + _type[1]
            
    def render(self, args=None):
        if args:
            self.setargs(args)
            
        result = []
        iif = [False]
        self.init(self.macro)
        for line in self:
            skip = iif[-1]
            if self.token() == "#":
                stmt = self.token()
                if stmt == "if":
                    var = self.rest().strip()
                    if self.hasvar(var):
                        skip = not self.getvar(var)
                    else:
                        skip = True
                    iif.append(skip)
                elif stmt == "match":
                    var = self.token()
                    if self.hasvar(var):
                        value = self.getvar(var)
                        if value:
                            pattern = self.rest().strip()[1:-1] # 따옴표를 없앤다.
                            value = re.search(pattern, value)
                        skip = not value
                    else:
                        skip = True
                    iif.append(skip)
                elif stmt == "else":
                    iif[-1] = not iif[-1]
                elif stmt == "endif":
                    iif.pop()
            elif not iif[-1]:
                if line:
                    result.append(self.renderLine(line))
        return "\n".join(result)
        
    def __call__(self, *args, **kwargs):
        return self.render(*args, **kwargs)
            
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# <default.il> 파일에 정의된 간이 메크로들을 등록한다.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

class MacroParser(object):
    ws_split_re = re.compile(r"[ \t]+")
    
    def __init__(self, data):
        self.lines = data.splitlines()
        self.lineno = 0
        self.macros = {}
        
    def __iter__(self):
        return self
        
    def next(self):
        try:
            line = self.lines[self.lineno]
            self.lineno += 1
            return line
        except IndexError:
            raise StopIteration()
        
    def back(self):
        if self.lineno > 0:
            self.lineno -= 1
            
    def parse_macro_title(self, title):
        row = self.ws_split_re.split(title)
        assert(len(row) > 1 and row[0] == "macro")
        return row[1], row[2:]
            
    def parse_macro(self, title):
        name, args = self.parse_macro_title(title)
        if args[0] == "=>":
            macro = self.macros[args[1]]
        else:
            body = []
            for line in self:
                line = line.strip()
                if line.startswith("end macro"):
                    break
                body.append(line)
            macro = LadderMacro("\n".join(body), args)
        self.macros[name] = macro
        
    def parse(self):
        for line in self:
            line = line.strip()
            if line.startswith("macro"):
                self.parse_macro(line)
            elif line.startswith("//"):
                pass
            elif line:
                print("처리할 수 없습니다", self.lineno, line)
        return self.macros
            
    @staticmethod
    def load(filename, encoding="utf-8", default=True):
        if default:
            filename = os.path.join(os.path.split(os.path.abspath(__file__))[0], filename)
        f = io.open(filename, "r", encoding=encoding)
        if sys.platform == "win32":
            if f.read(1) != '\ufeff':
                print("첫문자가 인코딩 표식이 아닙니다")
        data = f.read()
        f.close()
        return MacroParser(data).parse()
        
import os
macros = MacroParser.load("default.il")
        
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 간이 매크로 형태로 표현하기 어려운 복잡한 매크로를 정의한다.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

op_args = ["manual", "btnno", "cmdno"]

op_button = LadderMacro("""\
    LD ${manual[M0]}
    AND ${btnno[M0]}
#if cmdno
    OUT ${cmdno[M8]}
#else
    OUT M0
#endif
""", op_args)

op_lamp = LadderMacro("""\
#if cmdno
    LD ${cmdno[L8]}
#else
    LD SM401
#endif
    OUT ${btnno[L0]}
""", op_args)

def m_op_box(arglist):
    stack = []
    length = len(arglist) - 1
    if length > 1:
        stack = ["MRD"]*length
        stack[0] = "MPS"
        stack[-1] = "MPP"
        
    M = [getmacro("title")(arglist[0][0:1])]
    L = []
    manual = arglist[0][1]
    for args in arglist[1:]:
        args = [manual] + args[1:3]
        M.append(op_button(args))
        L.append(op_lamp(args))
    return "\n".join(M + L)
    
    
def m_conveyer_step(arglist):
    steps = MacroParser.load("step.il")
    
    M = []
    L = []
    argnames = arglist[0]
    for i in range(1, len(arglist) - 1):
        a = dict(zip(argnames, arglist[i]))
        b = dict(zip(argnames, arglist[i+1]))
        comment_func = [a["comment"], a["func"]]
    
        result = steps["auto"](comment_func + [a["stop"], a["manual"], a["cv"]])
        M.append(result)
    
        result = steps["begin_cycle"](comment_func + [a["stop"]])
        L.append(result)
        
        if a["type"] == "buffer":
            result = steps["buffer"](comment_func + [a["from"], a["to"], a["st"], a["err"], a["stop"], b["dv"], b["st"], b["stlock"]])
            L.append(result)
        if a["type"] == "nbuffer":
            result = steps["nbuffer"](comment_func + [a["from"], a["to"], a["st"], a["err"], a["stop"], b["dv"], b["st"], b["stlock"]])
            L.append(result)
        if a["type"] == "start dv":
            result = steps["start_dv"](comment_func + [a["from"], a["to"], a["st"], a["dv"], a["pm"], a["err"], a["stop"], b["st"], b["dv"], b["pm"]])
            L.append(result)
        if a["type"] == "end dv":
            result = steps["end_dv"](comment_func + [a["from"], a["to"], a["st"], a["dv"], a["pm"], a["err"], a["stop"], b["st"], b["dv"], b["pm"]])
            L.append(result)
        
        result = steps["end_cycle"](comment_func + [a["pm"]])
        L.append(result)
    return "\n".join(L)
    
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

g = globals().copy()
for name, func in g.items():
    if name.startswith("m_"):
        macros[name[2:]] = func

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

def getmacro(name):
    global macros
    
    name = name.replace(" ", "_")
    if name in macros:
        return macros[name]
    return None

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# 주어진 자료를 래더와 코멘트로 변환한다.
def splitLadderAndComment(data):
    ladder = []
    comment = []
    for line in data.splitlines():
        line = line.strip()
        if line.startswith(";;"):
            comment.append(line[2:])
        else:
            ladder.append(line)
    return ("\n".join(lst) for lst in (ladder, comment))
        
def datasplit(data):
    return [line.split("\t") for line in data.splitlines()]

# 줄 단위로 처리된다. 자료들은 탭으로 구분된다(시트에서 클립보드로 복사하면 그렇게 된다).
# 첫 칸이 매크로 이름이고, 나머지는 매크로에 대한 인수로 사용된다.
def run(data):
    last = ""
    code = []
    it = iter(datasplit(data))
    for row in it:
        if not row:
            continue
        name, args = row[0], row[1:]
        
        if args and args[0]:
            if not name:
                name = last
            last = name
            
            # 이름이 *표로 시작하면 여러 줄에 걸친 인수를 가질 수 있다.
            # 인수가 이어지는 경우에는 이름 자리를 비워두고
            # 이름 자리가 공백이 아니면 인수의 끝으로 처리된다.
            # 공백만 아니면 뭐라도 상관없지만, end.를 사용한다.
            if name[0] == "*":
                name = name[1:]
                L = [args]
                for args in it:
                    L.append(args[1:])
                    if args[0]:
                        break
                args = L
                
            _macro = getmacro(name)
            if _macro:
                code.append(_macro(args))
            else:
                print("unknown macro", str(name))
    code = "\n".join(code)
    return splitLadderAndComment(code)

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 
if __name__ == '__main__':
    for name in sorted(macros.keys()):
        print(name, macros[name])
    opbox = getmacro("opbox")
    r = opbox.render(["", "S/T4.Down"])
    print(r)
    #print(EmgcStop(["statement", "comment", "K2000", "X1000"]))
